
/**
 *	TrueMusic - simple Java music player
 *
 *	TrueMusic is free software; you can redistribute it
 *	and/or modify it under the terms of the GNU General
 *	Public License as published by the Free Software
 *	Foundation; either version 2 of the License or (at
 *	your option) any later version.
 *
 *	This program is distributed in the hope that it will be
 *	useful but WITHOUT ANY WARRANTY; without even the
 *	implied warranty of the MERCHANTABILITY or FITNESS FOR
 *	A PARTICULAR PURPOSE. See the GNU General Public
 *	License for more details.
 *
 *	Copyright (c)2005-2007 Ryan Macnak <rmacnak@gmail.com>
 */

package com.servequake.truemusic;

import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;

import org.xml.sax.*;
import org.xml.sax.helpers.*;
import javax.xml.parsers.*;

/**
 * Abstraction for consistent control behavior.
 */
public class Playlist extends DefaultHandler implements ListModel, PlaybackListener, Iterable<Audio>
{
	private List<Audio> tracks = new LinkedList<Audio>();
	Audio current = null;
	
	private boolean random = false;
	private boolean repeat = false;

	private PlayerListener l;
	private int volume = -20;

	String name = "Playlist";
	File f;

	public Playlist(String name, File f, PlayerListener l)
	{
		this.name = name;
		this.f = f;
		this.l = l;
	}
	
	public boolean equals(Object o)
	{
		return name.equals(o.toString());
	}
	
	public String toString()
	{
		return name;
	}

	public void play()
	{
		if(current!=null) current.play();
	}

	public void pause()
	{
		if(current!=null) current.pause();
	}

	public void stop()
	{
		if(current!=null) current.stop();
	}

	public void next()
	{
		if(tracks.size()==0)
		{
			current = null;
			l.updateCurrent(null);
		}

		int next = tracks.indexOf(current);

		if(random)
		{
			int r = (int) (Math.random()*tracks.size());
			if(r==next) next++;
			else next = r;
		}
		else next++;

		if( next>=tracks.size() )
		{
			if(repeat) next = 0;
			else 
			{
				if(current!=null) current.stop();
				return;
			}
		}

		if(current!=null) current.stop();
		current = tracks.get(next);
		current.play();

		l.updateCurrent(current);
		return;
	}

	public void previous()
	{
		if(tracks.size()==0)
		{
			current = null;
			l.updateCurrent(null);
		}

		int next = tracks.indexOf(current);

		next--;

		if( next<0 ) return;

		if(current!=null) current.stop();
		current = tracks.get(next);
		current.play();

		l.updateCurrent(current);
		return;
	}

	public void setVolume(int level)
	{
		volume = level;
		return;
	}

	public void seek(int frame)
	{
		if(current!=null) current.seek(frame);
		return;
	}

	public void setTrack(int pos)
	{
		if(tracks.size()==0)
		{
			current = null;
			l.updateCurrent(null);
		}

		if( pos<0 || pos>=tracks.size() ) return;

		if(current!=null) current.stop();
		current = tracks.get(pos);
		current.play();

		l.updateCurrent(current);
	}

	public void setTrack(Audio a)
	{
		setTrack( tracks.indexOf(a) );
	}

	public void setRandom(boolean flag)
	{
		random = flag;
	}

	public void setRepeat(boolean flag)
	{
		repeat = flag;
	}

	public void addTrack(String res) throws Exception
	{
		addTrack(res,"","","","0","0");
	}

	class element
	{
		String name="";
		Map<String,String> attributes = new HashMap<String,String>();
		element parent=null;
		List<element> children = new LinkedList<element>();
		String chars="";
	}
	
	element el=null;

	public void load() throws Exception
	{
		SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
		parser.parse( new FileInputStream(f), this );
	}

	public void startElement(String namespaceURI,String sName,String qName,Attributes attrs)throws SAXException
	{
		element e = new element();
		e.name=qName;
		e.parent=el;
	
		if(attrs!=null) for(int i=0; i<attrs.getLength(); i++)
		{
			e.attributes.put( attrs.getQName(i),attrs.getValue(i) );
		}
		if(el!=null)el.children.add(e);
		el = e;
	}

	public void characters(char buf[],int offset,int len) throws SAXException
	{
		String s = new String(buf,offset,len);
		el.chars = el.chars + s;
	}

	public void endElement(String namespace, String sName, String qName) throws SAXException
	{
		element e = el;
		el = e.parent;

		if(e.name.equals("entry"))
		{
			String title="",artist="",album="",res="",frames="0",mod="0";
	
			for(element c: e.children)
			{
				if(c.name.equals("title")) title = c.chars;
				if(c.name.equals("artist")) artist = c.chars;
				if(c.name.equals("album")) album = c.chars;
				if(c.name.equals("resource")) res = c.chars;
				if(c.name.equals("frames")) frames = c.chars;
				if(c.name.equals("last-modified")) mod = c.chars;
			}

			try
			{
				addTrack(res,title,artist,album,frames,mod);
			}
			catch(Exception ex){ex.printStackTrace();}
		}
	}
	
	public void save() throws IOException
	{
			OutputStream os = new FileOutputStream(f);
			PrintStream out = new PrintStream(os, false, "UTF-8");
	
			out.print("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?><playlist>");

			out.println("<name>");
			out.println(name);
			out.println("</name>");
			
			for(Audio a: tracks)
			{
				out.print("<entry><title>");
				out.print(a.getTitle());
				out.print("</title><artist>");
				out.print(a.getArtist());
				out.print("</artist><album>");
				out.print(a.getAlbum());
				out.print("</album><frames>");
				out.print(a.getFrames());
				out.print("</frames><resource>");
				out.print(a.getResource());
				out.print("</resource><last-modified>");
			try{	out.print(new File(a.getResource()).lastModified()); }catch(Exception e){out.print("0");}
				out.print("</last-modified></entry>");
			}

			out.print("</playlist>");
			out.flush();
			out.close();
	}

	//TODO: loop through Audio impl Classes
	private void addTrack(String res, String title, String artist, String album, String frames, String mod) throws Exception
	{
		String x = res.toLowerCase();
		Audio a = null;

		if(x.endsWith(".mp3"))
		{
			try
			{
				((Mp3Audio)(a = new Mp3Audio())).load(res,title,artist,album,frames,mod,this);
			}
			catch(Exception e)
			{
				(a = new JavaLayerAudio()).load(res,this);
			}
		}
		else if(x.endsWith(".wav"))
		{
			(a = new JavaLayerAudio()).load(res,this);
		}
		else if(x.endsWith(".aif") || x.endsWith(".au"))
		{
			(a = new AppletClipAudio()).load(res,this);
		}
		else if(x.endsWith(".m3u"))
		{
			File f = new File(res);
			InputStream is = new FileInputStream(f);
			BufferedReader in = new BufferedReader(new InputStreamReader(is));

			while(true)
			{
				String z = in.readLine();
				if(z==null)break;
				try
				{
					addTrack( z );
				}
				catch(Exception ex){}
			}

			in.close();
			return;
		}
		else return; //TODO: fail to try JavaLayerAudio?

		tracks.add(a);

		int pos = tracks.size() - 1;
		if(ldl!=null)ldl.intervalAdded(new ListDataEvent(this,ListDataEvent.INTERVAL_ADDED,0,0));
		
		if(current==null)
		{
			current = a;
			l.updateCurrent(current);
		}
		return;
	}
	
	public void removeTrack(int pos)
	{
		if(pos<0 || pos>=tracks.size()) return;

		Audio dead = tracks.get(pos);
		if(current==dead) next();

		tracks.remove(dead);
		ldl.intervalRemoved(new ListDataEvent(this,ListDataEvent.INTERVAL_REMOVED,pos,pos));

		l.playlistChanged();
		return;
	}

	public void removeTrack(Audio a)
	{
		removeTrack( tracks.indexOf(a) );
	}
//////////////////////////////

	public Iterator<Audio> iterator()
	{
		return tracks.iterator();
	}

///////////////////

	public void playbackStarted(){}
	public void playbackStopped(){}
	public void playbackError(){}

	public void frameCompleted(int frame)
	{
		l.frameCompleted(frame);
	}

	public void playbackCompleted()
	{
		next();
	}

	public int getVolume()
	{
		return volume;
	}

/////////////////////////////////////

	private ListDataListener ldl;

	public void addListDataListener(ListDataListener l)
	{
		this.ldl = l;
	}

	public Object getElementAt(int index)
	{
		return tracks.get(index);
	}

	public int getSize()
	{
		return tracks.size();
	}

	public void removeListDataListener(ListDataListener l)
	{
		ldl = null;
	}
}

